# coding:utf-8
import json
from datetime import datetime, date
from django.template import loader
from django.shortcuts import render
from django.views import View
# 可以只继承django.views.View，只实现Dispatch HTTP
from xadmin.views import ModelAdminView
# 继承xadmin的ModelAdminView，则可以和SomeModelAdmin关联，并在其中重载分发HTTP方法
# 如开启默认的importexport插件则可以在管理器中重载post方法拦截request
from django.http import HttpResponse, Http404, JsonResponse
from django.apps import apps
import xadmin
from xadmin.views import BaseAdminPlugin, ListAdminView
import numpy as np
import pandas as pd
from pandas._libs.tslibs.timestamps import Timestamp
from django.apps import apps
from django.db.models import fields
from django.db.models.fields import AutoField, BooleanField, AutoField, DateTimeField, DateField, CharField, EmailField, \
    IntegerField, FloatField, TextField, FilePathField
from django.db.models.fields.related import ForeignKey, OneToOneField, \
    ManyToManyField  # this model many_to_one that model
from django.db.models.fields.reverse_related import ForeignObjectRel, OneToOneRel, ManyToManyRel, ManyToOneRel
from django.db.models.fields.files import FileField, ImageField
from stdimage.models import StdImageField
from DjangoUeditor.models import UEditorField
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator

field_type_list = [
    FileField,
    ImageField,
    ForeignKey,
    OneToOneField,
    ManyToManyField,
    ForeignObjectRel,
    OneToOneRel,
    ManyToOneRel,
    ManyToManyRel,
    StdImageField,
    UEditorField,
    AutoField,
    BooleanField,
    CharField,
    TextField,
    EmailField,
    FilePathField,
    DateField,
    DateTimeField,
    IntegerField,
    FloatField,
].extend(fields.__all__)


def get_field_dtype(app_name, model_name, field_name):
    app = apps.get_app_config(app_name)
    model = app.get_model(model_name)
    field = model._meta.get_field(field_name)
    return {
        # EXCEL中记录文件本地或网络路径
        FileField: np.object,
        ImageField: np.object,
        StdImageField: np.object,
        # PRIMARY KEY 默认自增主键
        AutoField: np.int,  # Unable to convert column id to type <class 'int'>
        ForeignKey: np.int,  # 默认唯一标志AutoField
        # 非引用值，直接赋值给model
        BooleanField: np.bool,
        CharField: np.object,
        TextField: np.object,
        EmailField: np.object,
        UEditorField: np.object,
        IntegerField: np.int,
        FloatField: np.float,
        # DATE TIME 时间 np.datetime64 np.timedelta64
        DateField: np.datetime64,
        DateTimeField: np.datetime64,
    }[type(field)]


# 导入excel组件入口，如何在ModelAdmin中开启，插件模板如何插入，向插件传递参数
class ListImportExcelPlugin(BaseAdminPlugin):
    import_excel = False

    # 在xadmin.py中注册Admin管理器时，会覆盖插件plugin和视图view的属性和方法

    def init_request(self, *args, **kwargs):
        return bool(self.import_excel)

    def block_top_toolbar(self, context, nodes):
        # nodes.append(loader.render_to_string('xadmin/excel/model_list.top_toolbar.import.html', context={}))
        context_in_plugin = {
            'target_app': context['target_app'],
            'target_model': context['target_model'],
            'input_id': context['input_id'],
            'input_name': context['input_name'],
        }
        # view视图的context不能被插件调用，需要在插入插件模板的同时传入参数
        nodes.append(loader.render_to_string('xadmin/excel/upload.html', context=context_in_plugin))

    # view视图中@filter_hook修饰的方法表该方法可以被plugin插件截获
    def get_context(self, context):
        print(self.opts.label)
        target_app = self.opts.label.split('.')[0]
        target_model = self.opts.label.split('.')[1]
        context.update({
            'target_app': target_app,
            'target_model': target_model,
            'input_id': "{0}-{1}-excel".format(target_app, target_model),
            'input_name': "{0}-{1}-excel[]".format(target_app, target_model),
        })
        return context
    # {'app': self.model._meta.app_label, 'model': self.model._meta.model_name}


xadmin.site.register_plugin(ListImportExcelPlugin, ListAdminView)


# 集中处理url的配置
# 在项目urls.py中配置正则url，注意url和path的不同

class ImportExcelView(View):

    def post(self, request, app, model, *args, **kwargs):
        # url(r'^import/app/(?P<app>\w+)/model/(?P<model>\w+

        # app model fields
        target_app = apps.get_app_config(app)
        target_model = target_app.get_model(model)

        model_fields = {}
        field_dtype = {}
        required_fields = []
        datetime_fields = []
        foreign_fields = []

        for field in target_model._meta.get_fields(include_parents=True, include_hidden=False):
            if type(field) in [ForeignObjectRel, OneToOneRel, ManyToManyRel, ManyToOneRel, OneToOneField,
                               ManyToManyField]:
                continue
            else:
                model_fields[field.name] = field
                field_dtype[field.name] = get_field_dtype(app, model, field.name)
                print('field', field.name, field, type(field), field.blank)
                if not field.blank:
                    required_fields.append(field.name)
                if type(field) in [DateField, DateTimeField]:
                    datetime_fields.append(field.name)
                if type(field) is ForeignKey:
                    foreign_fields.append(field.name)

        excel = request.POST.get('input_name')
        # 插件截获视图的get_context方法，动态生成file input name，并更新view context
        # 然后再注入向插件注入所需的plugin context
        # plugin template使用plugin context，并通过ajax uploadextradata向服务器发送file input name
        # 服务器端通过file input name获取文件
        excel_file = request.FILES.get(excel)  # <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>

        # pandas 处理excel文件
        df = pd.read_excel(
            io=excel_file,
            dtype=field_dtype,
        )

        # data for frontend
        excel_rows = df.to_dict(orient='split')['data']
        excel_columns = df.to_dict(orient='split')['columns']

        if set(excel_columns) < set(required_fields):
            return JsonResponse({
                'status': 1,
                'messsage': '缺少必填项',
            }, safe=False)

        # data for backend
        records_for_model = df.to_dict(orient='records')

        for record in records_for_model:
            # 时间
            for field in [item for item in datetime_fields if item in excel_columns]:
                record[field] = record[field].to_pydatetime()
            # 外键
            for field in [item for item in foreign_fields if item in excel_columns]:
                related_model = model_fields[field].related_model
                record[field] = related_model.objects.get(id=record[field])
            # save
            # new_instance = target_model(**record)
            # new_instance.save()


        return JsonResponse({
            'status': 0,
            'table-head': excel_columns,
            'table-body': excel_rows,
        }, safe=False)


